package com.stone.config.interceptor;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stone.bean.AppResponse;
import com.stone.bean.vo.LoginVO;
import com.stone.config.ApplicationConfig;
import com.stone.config.SpringContextConfig;
import com.stone.config.exception.BusinessException;
import com.stone.constant.Const;
import com.stone.entity.SysMenu;
import com.stone.service.SysMenuService;
import com.stone.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * token
 */
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {

    private final RedisTemplate<String, String> redisTemplate;
    private final ApplicationConfig applicationConfig;
    private final SysUserService sysUserService;
    private final SysMenuService sysMenuService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (isAllowPath(request)) {
            return true;
        }
        String token = request.getHeader(Const.AUTH);
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(AppResponse.needLogin().setMessage("token为空"));
        }
        try {
            String redisKey = Const.AUTH_PREFIX + token;
            Object obj = redisTemplate.opsForValue().get(redisKey);
            if (Objects.isNull(obj)) {
                throw new BusinessException(AppResponse.needLogin().setMessage("token已过期"));
            }
            LoginVO logUser = JSON.parseObject(obj.toString(), LoginVO.class);
            boolean pbToken = sysUserService.checkUserToken(logUser.getId(), token);
            if (pbToken) {
                // 判断用户是否有url的访问权限
                if (!checkUserPermissionUrl(request.getServletPath(), logUser)) {
                    throw new BusinessException("无权访问");
                }
                redisTemplate.opsForValue().set(Const.AUTH_PREFIX + token, JSON.toJSONString(logUser), Const.AUTH_EXPIRE, TimeUnit.SECONDS);
            } else {
                throw new BusinessException(AppResponse.needLogin().setMessage("token已过期"));
            }
        } catch (Exception e) {
            log.error("token拦截器异常", e);
            throw e;
        }
        return true;
    }

    /**
     * 判断用户是否有权限访问当前uri
     */
    private boolean checkUserPermissionUrl(String requestUri, LoginVO logUser) {
        if (SpringContextConfig.isTest() && SpringContextConfig.isWindows()) {
            return true;
        }
        List<SysMenu> sysMenuList = sysMenuService.list(new QueryWrapper<SysMenu>()
                .eq("enable", true)
                .eq("type", Const.Menu.button.type)
        );
        List<String> urlList = sysMenuList.stream().map(SysMenu::getUrl).collect(Collectors.toList());
        if (CollUtil.isEmpty(urlList) || !urlList.contains(requestUri)) {
            // 如果requestUri未加入权限控制，则无需再判断
            return true;
        }
        List<String> userPermissionsUrls = logUser.getUrlList();
        if (CollUtil.isEmpty(userPermissionsUrls)) {
            return false;
        }
        for (String permissionUrl : userPermissionsUrls) {
            // 如果权限是* 结尾 说明忽略后面的路径
            if (permissionUrl.endsWith("*")) {
                String tmpUrl = permissionUrl.replace("*", "");
                if (requestUri.startsWith(tmpUrl)) {
                    return true;
                }
            } else {
                if (requestUri.startsWith(permissionUrl)) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 判断请求URI是不是白名单中的URI
     */
    private boolean isAllowPath(HttpServletRequest request) {
        String requestURI = request.getServletPath();
        if (requestURI.contains("swagger") || requestURI.contains(".png") || requestURI.contains("api-docs")) {
            return true;
        }
        if (request.getMethod().equals("OPTIONS")) {
            return true;
        }
        boolean flag = false;
        if (applicationConfig.getTokenAllowPath() == null) {
            return false;
        }
        for (String allowPath : applicationConfig.getTokenAllowPath()) {
            if (allowPath.endsWith("*")) {
                allowPath = allowPath.replace("/**", "");
            }
            if (requestURI.startsWith(allowPath)) {
                flag = true;
                break;
            }
        }
        return flag;
    }

    public AuthInterceptor(RedisTemplate redisTemplate, ApplicationConfig applicationConfig, SysUserService sysUserService, SysMenuService sysMenuService) {
        this.redisTemplate = redisTemplate;
        this.applicationConfig = applicationConfig;
        this.sysUserService = sysUserService;
        this.sysMenuService = sysMenuService;
    }
}

